/*
	This file is part of Erosion, a clone of Qix
	Copyright (c) 2009 Bill Whitacre

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/

#include "shader.h"

static PFNGLDELETEOBJECTARBPROC                    glDeleteObjectARB=NULL;
static PFNGLGETHANDLEARBPROC                       glGetHandleARB=NULL;
static PFNGLDETACHOBJECTARBPROC                    glDetachObjectARB=NULL;
static PFNGLCREATESHADEROBJECTARBPROC              glCreateShaderObjectARB=NULL;
static PFNGLSHADERSOURCEARBPROC                    glShaderSourceARB=NULL;
static PFNGLCOMPILESHADERARBPROC                   glCompileShaderARB=NULL;
static PFNGLCREATEPROGRAMOBJECTARBPROC             glCreateProgramObjectARB=NULL;
static PFNGLATTACHOBJECTARBPROC                    glAttachObjectARB=NULL;
static PFNGLLINKPROGRAMARBPROC                     glLinkProgramARB=NULL;
static PFNGLUSEPROGRAMOBJECTARBPROC                glUseProgramObjectARB=NULL;
static PFNGLVALIDATEPROGRAMARBPROC                 glValidateProgramARB=NULL;

static PFNGLUNIFORM1FARBPROC                       glUniform1fARB=NULL;
static PFNGLUNIFORM2FARBPROC                       glUniform2fARB=NULL;
static PFNGLUNIFORM3FARBPROC                       glUniform3fARB=NULL;
static PFNGLUNIFORM4FARBPROC                       glUniform4fARB=NULL;
static PFNGLUNIFORM1IARBPROC                       glUniform1iARB=NULL;
static PFNGLUNIFORM2IARBPROC                       glUniform2iARB=NULL;
static PFNGLUNIFORM3IARBPROC                       glUniform3iARB=NULL;
static PFNGLUNIFORM4IARBPROC                       glUniform4iARB=NULL;
static PFNGLUNIFORM1FVARBPROC                      glUniform1fvARB=NULL;
static PFNGLUNIFORM2FVARBPROC                      glUniform2fvARB=NULL;
static PFNGLUNIFORM3FVARBPROC                      glUniform3fvARB=NULL;
static PFNGLUNIFORM4FVARBPROC                      glUniform4fvARB=NULL;
static PFNGLUNIFORM1IVARBPROC                      glUniform1ivARB=NULL;
static PFNGLUNIFORM2IVARBPROC                      glUniform2ivARB=NULL;
static PFNGLUNIFORM3IVARBPROC                      glUniform3ivARB=NULL;
static PFNGLUNIFORM4IVARBPROC                      glUniform4ivARB=NULL;
static PFNGLUNIFORMMATRIX2FVARBPROC                glUniformMatrix2fvARB=NULL;
static PFNGLUNIFORMMATRIX3FVARBPROC                glUniformMatrix3fvARB=NULL;
static PFNGLUNIFORMMATRIX4FVARBPROC                glUniformMatrix4fvARB=NULL;

static PFNGLGETOBJECTPARAMETERFVARBPROC            glGetObjectParameterfvARB=NULL;
static PFNGLGETOBJECTPARAMETERIVARBPROC            glGetObjectParameterivARB=NULL;
static PFNGLGETINFOLOGARBPROC                      glGetInfoLogARB=NULL;
static PFNGLGETUNIFORMLOCATIONARBPROC              glGetUniformLocationARB=NULL;
static PFNGLGETACTIVEUNIFORMARBPROC                glGetActiveUniformARB=NULL;
static PFNGLGETUNIFORMFVARBPROC                    glGetUniformfvARB=NULL;
static PFNGLGETUNIFORMIVARBPROC                    glGetUniformivARB=NULL;
static PFNGLGETSHADERSOURCEARBPROC                 glGetShaderSourceARB=NULL;

static int extensions_loaded=0;

static int load_shader_extensions()
{
	if (extensions_loaded) return 1;

	if (!(glDeleteObjectARB=load_extension("glDeleteObjectARB"))) return 0;
	if (!(glGetHandleARB=load_extension("glGetHandleARB"))) return 0;
	if (!(glDetachObjectARB=load_extension("glDetachObjectARB"))) return 0;
	if (!(glCreateShaderObjectARB=load_extension("glCreateShaderObjectARB"))) return 0;
	if (!(glShaderSourceARB=load_extension("glShaderSourceARB"))) return 0;
	if (!(glCompileShaderARB=load_extension("glCompileShaderARB"))) return 0;
	if (!(glCreateProgramObjectARB=load_extension("glCreateProgramObjectARB"))) return 0;
	if (!(glAttachObjectARB=load_extension("glAttachObjectARB"))) return 0;
	if (!(glLinkProgramARB=load_extension("glLinkProgramARB"))) return 0;
	if (!(glUseProgramObjectARB=load_extension("glUseProgramObjectARB"))) return 0;
	if (!(glValidateProgramARB=load_extension("glValidateProgramARB"))) return 0;

	printf("loaded GPU program object functions.\n");

	if (!(glUniform1fARB=load_extension("glUniform1fARB"))) return 0;
	if (!(glUniform2fARB=load_extension("glUniform2fARB"))) return 0;
	if (!(glUniform3fARB=load_extension("glUniform3fARB"))) return 0;
	if (!(glUniform4fARB=load_extension("glUniform4fARB"))) return 0;
	if (!(glUniform1iARB=load_extension("glUniform1iARB"))) return 0;
	if (!(glUniform2iARB=load_extension("glUniform2iARB"))) return 0;
	if (!(glUniform3iARB=load_extension("glUniform3iARB"))) return 0;
	if (!(glUniform4iARB=load_extension("glUniform4iARB"))) return 0;
	if (!(glUniform1fvARB=load_extension("glUniform1fvARB"))) return 0;
	if (!(glUniform2fvARB=load_extension("glUniform2fvARB"))) return 0;
	if (!(glUniform3fvARB=load_extension("glUniform3fvARB"))) return 0;
	if (!(glUniform4fvARB=load_extension("glUniform4fvARB"))) return 0;
	if (!(glUniform1ivARB=load_extension("glUniform1ivARB"))) return 0;
	if (!(glUniform2ivARB=load_extension("glUniform2ivARB"))) return 0;
	if (!(glUniform3ivARB=load_extension("glUniform3ivARB"))) return 0;
	if (!(glUniform4ivARB=load_extension("glUniform4ivARB"))) return 0;
	if (!(glUniformMatrix2fvARB=load_extension("glUniformMatrix2fvARB"))) return 0;
	if (!(glUniformMatrix3fvARB=load_extension("glUniformMatrix3fvARB"))) return 0;
	if (!(glUniformMatrix4fvARB=load_extension("glUniformMatrix4fvARB"))) return 0;

	printf("loaded GPU program argument functions.\n");

	if (!(glGetObjectParameterfvARB=load_extension("glGetObjectParameterfvARB"))) return 0;
	if (!(glGetObjectParameterivARB=load_extension("glGetObjectParameterivARB"))) return 0;
	if (!(glGetInfoLogARB=load_extension("glGetInfoLogARB"))) return 0;
	if (!(glGetUniformLocationARB=load_extension("glGetUniformLocationARB"))) return 0;
	if (!(glGetActiveUniformARB=load_extension("glGetActiveUniformARB"))) return 0;
	if (!(glGetUniformfvARB=load_extension("glGetUniformfvARB"))) return 0;
	if (!(glGetUniformivARB=load_extension("glGetUniformivARB"))) return 0;
	if (!(glGetShaderSourceARB=load_extension("glGetShaderSourceARB"))) return 0;

	printf("loaded GPU program attribute access functions.\nall GPU program functions loaded successfully!\n");

	extensions_loaded=1;
	return 1;
}

static char *readTextFile(char *fn)
{
	FILE *fp;
	char *content = NULL;

	int count=0;

	if (fn != NULL) {

		fp = fopen(fn,"rt");

		if (fp != NULL) {

    	      		fseek(fp, 0, SEEK_END);
    			count = ftell(fp);
    			rewind(fp);

			if (count > 0) {
				content = (char *)malloc(sizeof(char) * (count+1));
				count = fread(content,sizeof(char),count,fp);
				content[count] = '\0';
			}
			fclose(fp);
		}
	}

	return content;
}

static void print_info_log(GLhandleARB obj,FILE *_log)
{
    int infologLength = 0;
    int charsWritten  = 0;
    char *infoLog;
    glGetObjectParameterivARB(obj,GL_OBJECT_INFO_LOG_LENGTH_ARB,&infologLength);
    if (infologLength > 1)
    {
        infoLog = (char *)malloc(infologLength);
        glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);
		fprintf(_log,"info log:\n%s\n",infoLog);
        free(infoLog);
    }
}

rat_shader *rat_load_shader(char *dir,const char *name,FILE *log_file)
{
	GLhandleARB program,frag,vert;
	char frag_filename[5000];
	char vert_filename[5000];
	char *frag_source,*vert_source;
	rat_shader *shader;

	if (!extensions_loaded)
	{
		if (!load_shader_extensions())
		{
			printf("failed to load GPU program extension!\n");
			return NULL;
		}
	}

	program=glCreateProgramObjectARB();

	frag=glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
	vert=glCreateShaderObjectARB(GL_VERTEX_SHADER);

	strcpy(frag_filename,dir);
	strcat(frag_filename,"/");
	strcat(frag_filename,name);
	strcat(frag_filename,".frag");

	strcpy(vert_filename,dir);
	strcat(vert_filename,"/");
	strcat(vert_filename,name);
	strcat(vert_filename,".vert");

	frag_source=readTextFile(frag_filename);
	if (frag_source==NULL)
	{
		printf("could not load fragment program: %s\n",frag_filename);
		return NULL;
	}
	else
		printf("loaded fragment program: %s\n",frag_filename);
	vert_source=readTextFile(vert_filename);
	if (vert_source==NULL)
	{
		printf("could not load vertex program: %s\n",vert_filename);
		return NULL;
	}
	else
		printf("loaded vertex program: %s\n",vert_filename);

	glShaderSourceARB(frag,1,(const GLcharARB **)&frag_source,NULL);
	glShaderSourceARB(vert,1,(const GLcharARB **)&vert_source,NULL);

	free(frag_source); free(vert_source);

	glCompileShaderARB(frag); printf("fragment program compiled.\n");
	glCompileShaderARB(vert); printf("vertex program compiled.\n");

	glAttachObjectARB(program,frag);
	glAttachObjectARB(program,vert);

	glLinkProgramARB(program);

	printf("GPU program linked.\n");

	shader=(rat_shader *)malloc(sizeof(rat_shader));

	shader->dumped=0;
	shader->program_handle=program;
	shader->vert=vert; shader->frag=frag;
	shader->_log=log_file;

	printf("shader object created.\n");

	return shader;
}

void rat_destroy_shader(rat_shader *shader)
{
	glDeleteObjectARB(shader->vert);
	glDeleteObjectARB(shader->frag);
	glDeleteObjectARB(shader->program_handle);
	free(shader);

	printf("destroyed shader object and GPU program.\n");
}

void rat_apply_shader(rat_shader *shader)
{
	if (!shader)
		glUseProgramObjectARB(0);
	else
	{
		glUseProgramObjectARB(shader->program_handle);
		if (!shader->dumped)
		{
			print_info_log(shader->program_handle,shader->_log);
			shader->dumped=1;
		}
	}
}

void rat_shader_set_uniform_floats(rat_shader *shader,char *name,int num,float *floats)
{
	GLint addr=glGetUniformLocationARB(shader->program_handle,name);
	if (addr>-1)
	{
		switch (num)
		{
		case 1:
			glUniform1fARB(addr,floats[0]);
			break;
		case 2:
			glUniform2fARB(addr,floats[0],floats[1]);
			break;
		case 3:
			glUniform3fARB(addr,floats[0],floats[1],floats[2]);
			break;
		case 4:
			glUniform4fARB(addr,floats[0],floats[1],floats[2],floats[3]);
			break;
		default:
			break;
		};
	}
}

void rat_shader_set_uniform_ints(rat_shader *shader,char *name,int num,int *ints)
{
	GLint addr=glGetUniformLocationARB(shader->program_handle,name);
	if (addr>-1)
	{
		switch (num)
		{
		case 1:
			glUniform1iARB(addr,ints[0]);
			break;
		case 2:
			glUniform2iARB(addr,ints[0],ints[1]);
			break;
		case 3:
			glUniform3iARB(addr,ints[0],ints[1],ints[2]);
			break;
		case 4:
			glUniform4iARB(addr,ints[0],ints[1],ints[2],ints[3]);
			break;
		default:
			break;
		};
	}
}

void rat_shader_set_uniform_matrix_2x2(rat_shader *shader,char *name,float *mat)
{
	GLint addr=glGetUniformLocationARB(shader->program_handle,name);
	if (addr>-1)
		glUniformMatrix2fvARB(addr,1,GL_FALSE,mat);
}

void rat_shader_set_uniform_matrix_3x3(rat_shader *shader,char *name,float *mat)
{
	GLint addr=glGetUniformLocationARB(shader->program_handle,name);
	if (addr>-1)
		glUniformMatrix3fvARB(addr,1,GL_FALSE,mat);
}

void rat_shader_set_uniform_matrix_4x4(rat_shader *shader,char *name,float *mat)
{
	GLint addr=glGetUniformLocationARB(shader->program_handle,name);
	if (addr>-1)
		glUniformMatrix4fvARB(addr,1,GL_FALSE,mat);
}

